<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8">
  <title>华容道</title>
  <style>
    #app {
      position: relative;
      margin: auto;
      width: 400px;
    }

    .play>.edit {
      display: none;
    }

    .edit>.play {
      display: none;
    }

    #control {
      display: flex;
    }

    #control>div {
      flex: 1;
      text-align: center;
      margin: 10px 5px;
    }

    #editBoard {
      border: 1px solid black;
    }

    #control>div.btn {
      cursor: pointer;
      border: 1px solid black;
    }

    #board,
    #editBoard {
      position: relative;
      margin: auto;
      width: 400px;
      height: 500px;
    }

    .piz {
      position: absolute;
      border: 2px solid black;
      margin: 2px;
      background-color: rgb(39, 160, 130);
      display: flex;
      justify-content: center;
      align-items: center;
    }
  </style>
</head>

<body>
  <div id="app" class="play">
    <div id="control" class="control">
      <div id="editBtn" class="btn">设计</div>
      <div id="reset" class="btn">重来</div>
      <div id="back" class="btn">回退</div>
      <div id="forward" class="btn">慢慢想</div>
      <div>第 <span id="stepNo">0</span> 步</div>
    </div>
    <div id="board" class="play"></div>
    <div id="editBoard" class="edit"></div>
  </div>
  <script>
    const size = 110
    const STATUSES = { "经典局面": 3, "三军联防": 2583700119 }
    const editConfig = {
      inEdit: false, ps: [], x: 0, y: 0, typeCount: [4, 5, 5, 1, 2], colHight: [0, 0, 0, 0],
      officerName: ['关羽', '张飞', '赵云', '马超', '黄忠'],
      typeSize: [{ w: 1, h: 1, n: '兵' }, { w: 1, h: 2, n: '' }, { w: 2, h: 1, n: '' }, { w: 2, h: 2, n: '曹操' }, { w: 1, h: 1, n: '空格' }],
      freeType: function (p) {
        console.log("free type, p and this:", p, editConfig);
        const { x, y, t } = p
        const { w, h, n } = editConfig.typeSize[t]
        // 释放行列占位
        editConfig.x = x
        editConfig.y = y
        // 释放列高度
        for (let i = 0; i < w; i++) {
          editConfig.colHight[x + i] -= h
        }
        // 释放类型数量
        editConfig.typeCount[t] += 1
        if (n.length === 0) { editConfig.typeCount[3 - t] += 1 }
      },
      useType: function (p, t) {
        const { x, y } = p
                const { w, h, n } = editConfig.typeSize[t]
        // 增加列高度
        for (let i = 0; i < w; i++) {
          editConfig.colHight[x + i] += h
        }
        // 行列占位
        editConfig.x += w
        while (editConfig.x === 4 || editConfig.colHight[editConfig.x] > editConfig.y) {
          if (editConfig.x == 4) { editConfig.y += 1; editConfig.x = 0 } else { editConfig.x += 1 }
        }
        // 消耗类型数量
        editConfig.typeCount[t] -= 1
        if (n.length === 0) { editConfig.typeCount[3 - t] -= 1 }
        p.t = t
        // 类型名称
        p.name = n
        if (n.length === 0) editConfig.changeName(p)
      },
      changeName: function (piz) {
        // 只有将军才可以改名
        if (piz.t < 1 || piz.t > 2) return
        // 将军名称集合
        const ns = editConfig.officerName.slice()
        // 移除已经被占用的名称（不包括当前将军）
        editConfig.ps.forEach(p => { if (p.name != piz.name && ns.includes(p.name)) ns.splice(ns.indexOf(p.name), 1) })
        // 选用下一个名称
        if (ns.length > 0) { piz.name = ns[(ns.indexOf(piz.name) + 1) % ns.length] }
      },
      nextTypes: function (current) {
        const { x, y, colHight, ps, typeCount } = editConfig
        const c = current === undefined ? -1 : current;
        console.log("next types, current:", current, c)
        const ts = [0, 4];
        console.log("colHight:", [...colHight], x, y)
        if (x < 3 && colHight[x + 1] === y) { ts.push(2); ts.push(3) }
        console.log("ts 增加横向:", ts)
        if (y < 4) { ts.push(1) } else if ( ts.length === 4 ) { ts.pop() }
        console.log("ts 增加竖向:", ts)
        const types = ts.filter(t => typeCount[t] > 0).map(t => t > c ? t : t + 5)
        console.log("types 排序前:", types)
        types.sort((a, b) => a - b)
        console.log("types 排序后:", types)
        return types.map(t => t % 5)
      },
      addPiece: function () {
        const { x, y, colHight, ps, typeCount, typeSize } = editConfig
        const t = editConfig.nextTypes()[0];
        editConfig.useType({ x, y }, t);
        const p = { x, y, t, name: typeSize[t].n }
        // 增加棋子
        editConfig.ps.push(p)
        return p
      }
    };
    editBtn.addEventListener('click', function () {
      if (editConfig.inEdit) {
        app.classList.remove('edit')
        app.classList.add('play')
        editBoard.innerHTML = ''
        editConfig.inEdit = false
      } else {
        editConfig.inEdit = true
        app.classList.remove('play')
        app.classList.add('edit')
        editBoard.innerHTML = ''
        editConfig.ps = []
        editConfig.x = 0
        editConfig.y = 0
        editConfig.typeCount = [4, 5, 5, 1, 2]
        editConfig.colHight = [0, 0, 0, 0]
      }
    })
    editBoard.addEventListener('click', function (e) {
      if (editConfig.ps.length < 12) {
        const typeSize = editConfig.typeSize
        const p = editConfig.addPiece();
        const div = document.createElement('div')
        const button = document.createElement('button');
        button.textContent = p.name;
        button.addEventListener('click', function (event) {
          console.log("piece name clicked", p)
          event.stopPropagation();
          editConfig.changeName(p)
          button.textContent = p.name
        })
        div.appendChild(button)
        div.className = 'piz'
        div.style.left = (p.x * size) + 'px'
        div.style.top = (p.y * size) + 'px'
        div.style.height = (editConfig.typeSize[p.t].h * size - 8) + 'px'
        div.style.width = (editConfig.typeSize[p.t].w * size - 8) + 'px'
        div.style.setProperty('--id', editConfig.ps.length - 1)
        div.addEventListener('click', function (event) {
          event.stopPropagation();
          if (Number(div.style.getPropertyValue('--id')) < editConfig.ps.length - 1) { return false }
          console.log("寻找下一个可放类型", editConfig.x, editConfig.y)
          const oldT = p.t
          editConfig.freeType(p)
          console.log("释放当前类型", editConfig.x, editConfig.y)
          const ts = editConfig.nextTypes(oldT)
          editConfig.useType(p, ts[0])
          div.style.height = (editConfig.typeSize[p.t].h * size - 8) + 'px'
          div.style.width = (editConfig.typeSize[p.t].w * size - 8) + 'px'
          button.textContent = p.name
          return false
        })
        editBoard.appendChild(div)
        if (editConfig.ps.length === 12) {
          editConfig.inEdit = false
          app.classList.remove('edit')
          app.classList.add('play')
          const ps = editConfig.ps.map(p => ({ ...p }))
          ps.sort((a, b) => a.t - b.t)
          pizs.splice(0, pizs.length, ...ps.map(p => [p.x, p.y, editConfig.typeSize[p.t].h, editConfig.typeSize[p.t].w, p.name]))
          steps.splice(0)
          pos.splice(0, pos.length, ...pizs.map(p => [p[0], p[1], 0]))
          refresh()
        }
      }
    })
    const pizs = [[1, 2, 1, 2, '关羽'], [1, 3, 1, 1, '兵'],
    [2, 3, 1, 1, '兵'], [0, 4, 1, 1, '兵'], [3, 4, 1, 1, '兵'],
    [0, 0, 2, 1, '张飞'], [3, 0, 2, 1, '赵云'], [0, 2, 2, 1, '马超'],
    [3, 2, 2, 1, '黄忠'], [1, 0, 2, 2, '曹操']] // x,y,h,w,name
    const steps = [] // 记录每一步的棋子位置
    const pos = [...pizs.map(p => [p[0], p[1], 0]), [1, 4, 0], [2, 4, 0]]
    // (x, y) 的位置是否为空
    const xyIsEmpty = (x, y, ps) => [ps[10], ps[11]].some(p => p[0] === x && p[1] === y)
    // 高阶函数，参数为棋子的索引，棋子的宽度和高度，返回一个函数，该函数判断该棋子是否可以左移
    const canL = (i, w, h) => ((ps) => ps[i][0] > 0 && (xyIsEmpty(ps[i][0] - 1, ps[i][1], ps) && (h === 1 || xyIsEmpty(ps[i][0] - 1, ps[i][1] + 1, ps))))
    // 高阶函数，参数为棋子的索引，棋子的宽度和高度，返回一个函数，该函数判断该棋子是否可以上移
    const canU = (i, w, h) => ((ps) => ps[i][1] > 0 && (xyIsEmpty(ps[i][0], ps[i][1] - 1, ps) && (w === 1 || xyIsEmpty(ps[i][0] + 1, ps[i][1] - 1, ps))))
    // 高阶函数，参数为棋子的索引，棋子的宽度和高度，返回一个函数，该函数判断该棋子是否可以右移
    const canR = (i, w, h) => ((ps) => ps[i][0] < 4 && (xyIsEmpty(ps[i][0] + w, ps[i][1], ps) && (h === 1 || xyIsEmpty(ps[i][0] + w, ps[i][1] + 1, ps))))
    // 高阶函数，参数为棋子的索引，棋子的宽度和高度，返回一个函数，该函数判断该棋子是否可以下移
    const canD = (i, w, h) => ((ps) => ps[i][1] + h < 5 && (xyIsEmpty(ps[i][0], ps[i][1] + h, ps) && (w === 1 || xyIsEmpty(ps[i][0] + 1, ps[i][1] + h, ps))))
    const canMove = (who, d) => {
      const can = [canL, canU, canR, canD][(d + pos[who][2]) % 4](who, pizs[who][3], pizs[who][2])
      return can(pos)
    }

    // （x, y）的位置对应的空格的索引
    const idxOfEmptyXy = (x, y, ps) => ps[10][0] === x && ps[10][1] === y ? 10 : ps[11][0] === x && ps[11][1] === y ? 11 : -1
    const moveL = (i, ps) => {
      const idx = idxOfEmptyXy(ps[i][0] - 1, ps[i][1], ps)
      if (idx !== -1) {
        if (pizs[i][2] === 2) {
          ps[21 - idx][0] += pizs[i][3]
        }
        ps[idx][0] += pizs[i][3]
        ps[i][0] -= 1
      }
    }
    const moveU = (i, ps) => {
      const idx = idxOfEmptyXy(ps[i][0], ps[i][1] - 1, ps)
      if (idx !== -1) {
        if (pizs[i][3] === 2) {
          ps[21 - idx][1] += pizs[i][2]
        }
        ps[idx][1] += pizs[i][2]
        ps[i][1] -= 1
      }
    }
    const moveR = (i, ps) => {
      const idx = idxOfEmptyXy(ps[i][0] + pizs[i][3], ps[i][1], ps)
      if (idx !== -1) {
        if (pizs[i][2] === 2) {
          ps[21 - idx][0] -= pizs[i][3]
        }
        ps[idx][0] -= pizs[i][3]
        ps[i][0] += 1
      }
    }
    const moveD = (i, ps) => {
      const idx = idxOfEmptyXy(ps[i][0], ps[i][1] + pizs[i][2], ps)
      if (idx !== -1) {
        if (pizs[i][3] === 2) {
          ps[21 - idx][1] -= pizs[i][2]
        }
        ps[idx][1] -= pizs[i][2]
        ps[i][1] += 1
      }
    }
    const move = (who, d) => {
      // 棋子与空格的原始位置（含移动方向），当与上一步的棋子相同时，弹出历史记录，否则构造
      const s = steps.length > 0 && steps[steps.length - 1][0][0] === who ? steps.pop().map(p => p.slice(0, 4)) : [who, 10, 11].map(i => [i, ...pos[i]])
      // 移动棋子的函数（已绑定方向）
      const f = [moveL, moveU, moveR, moveD][(d + pos[who][2]) % 4]
      // 移动棋子
      f(who, pos)
      // 更新棋子的新移动方向
      pos[who][2] = (d + pos[who][2] + 3) % 4
      // 记录步骤历史，每个步骤棋子及空格原位置之后追加新位置
      steps.push(s.map(p => [...p, ...pos[p[0]]]))
    }

    const refresh = () => {
      for (let div of board.children) {
        const i = Number(div.style.getPropertyValue('--id'))
        const p = pos[i]
        div.style.left = (p[0] * size) + 'px'
        div.style.top = (p[1] * size) + 'px'
        div.style.height = (pizs[i][2] * size - 8) + 'px'
        div.style.width = (pizs[i][3] * size - 8) + 'px'
        div.textContent = pizs[i][4]
      }
      // 更新步数
      stepNo.innerHTML = steps.length
    }

    for (let i = 0; i < pizs.length; i++) {
      const div = document.createElement('div')
      const p = pizs[i]
      div.textContent = i + ' ' + p[4]
      div.style.height = (p[2] * size - 8) + 'px'
      div.style.width = (p[3] * size - 8) + 'px'
      div.style.setProperty('--id', i)
      div.className = 'piz '
      div.addEventListener('click', function () {
        for (let d = 0; d < 4; d++) {
          if (canMove(i, d)) {
            move(i, d)
            refresh()
            break
          }
        }
      });
      board.appendChild(div);
    }
    back.addEventListener('click', function () {
      if (editConfig.inEdit) {
        const div = editBoard.querySelector(`div[style*="--id:${editConfig.ps.length - 1}"]`)
        if (div) { editBoard.removeChild(div) }
        const p = editConfig.ps.pop()
        editConfig.freeType(p)
        return
      }
      if (steps.length > 0) {
        const s = steps.pop()
        s.forEach(p => {
          pos[p[0]] = [...p.slice(1, 4)]
        })
      }
      refresh()
      stepNo.innerHTML = steps.length
    })
    reset.addEventListener('click', () => { pos.splice(0, pos.length, ...pizs.map(p => [p[0], p[1], 0])); steps.splice(0); refresh() })
    refresh();
  </script>
  <script type="module">
    import init, { query } from './pkg/huarongdao.js';

    const tip_step = (step) => {
      let ms = []
      pos.forEach((piece, idx) => {
        let pos = piece[0] + piece[1] * 4;
        for (let i = 0; i < step.length; i += 2) {
          if (step[i] === pos) {
            ms.push([idx, ...piece, step[i + 1] % 4, Math.floor(step[i + 1] / 4), piece[3]])
          }
        }
      });
      ms.forEach((p) => { pos[p[0]][0] = p[4]; pos[p[0]][1] = p[5] });
      steps.push(ms);
      refresh();
    }
    init().then(() => {
      forward.addEventListener('click', async () => {
        if (pos[9][1] === 3 && pos[9][0] === 1) {
          alert('恭喜，您已经成功把曹操释放了！');
          return;
        }
        const stepNo = steps.length;
        // 对棋子宽高进行编码
        const flag = (i) => i < 10 ? pizs[i][3] * 2 + pizs[i][2] - 3 : 4;
        const b = Array.from(Array.from(pos.map(p => p[0] + p[1] * 4).entries()).map(([i, p]) => [p, flag(i)]).sort((a, b) => a[0] - b[0]).entries());
        const z = b.filter(([i, v]) => v[1] === 4).map(([i, v]) => i)
        const o = b.map(([i, v]) => (v[1] & 3) << (i * 2))
        const n = z[0] << 28 | z[1] << 24 | o.reduce((a, b) => a | b, 0);

        console.log(Number(BigInt(n) & BigInt(0xFFFFFFFFn)));
        const result = await query(n);
        if (stepNo === steps.length) {
          tip_step(result);
        }
      });
      forward.innerHTML = '提示一下';
    })

  </script>
</body>

</html>